/*
 * CmdOption.cc
 *
 *  Created on: Jan 13, 2011
 *      Author: Chengnian SUN
 */
#include "CmdOption.h"

const unsigned DEFAULT_TRAINING_REPORTS = 200;

/**
 * TAG 是命令行命令简写
 */

#define OP_INDEXING_TYPE "indexing-type"
#define OP_INDEXING_TYPE_TAG "indexing-type"

#define OP_DETECTOR_TYPE "detector"
#define OP_DETECTOR_TYPE_TAG "detector,d"

#define OP_EVOLVE_MODEL "evolve"
#define OP_EVOLVE_MODEL_TAG "evolve,e"

#define OP_DETECT_ALL_REPORTS "all-reports"
#define OP_DETECT_ALL_REPORTS_TAG "all-reports"

#define OP_TRAINING_DUPLICATES "training-duplicates"
#define OP_TRAINING_DUPLICATES_TAG OP_TRAINING_DUPLICATES

#define OP_TOP_K "topk"
#define OP_TOP_K_TAG "topk,k"

#define OP_LIMIT_K "limitl"
#define OP_LIMIT_K_TAG "limitl,l"


#define OP_START_TRAIN "startTrains"
#define OP_START_TRAIN_TAG "startTrains,s"

#define OP_END_TRAIN "endTraine"
#define OP_END_TRAIN_TAG "endTraine,e"

#define OP_ITERATION "iteration"
#define OP_ITERATION_TAG "iteration,i"

#define OP_NAME "name"
#define OP_NAME_TAG "name,n"

#define OP_FEATURE_CALCULATOR_TYPE "feature-calculator-type"
#define OP_FEATURE_CALCULATOR_TYPE_TAG "feature-calculator-type,f"

#define OP_PLAIN_MEASURE_TYPE "plain-measure-type"
#define OP_PLAIN_MEASURE_TYPE_TAG "plain-measure-type,p"

#define OP_HELP "help"
#define OP_HELP_TAG "help,h"

#define OP_VERSION "version"
#define OP_VERSION_TAG "version,v"

#define OP_RECOMMENDATION "recommend"           // --recommend 命令行参数
#define OP_RECOMMENDATION_TAG "recommend"

#define OP_DS "ds"
#define OP_DS_TAG "ds"

#define OP_RANKNET_CONFIG "ranknetconfig"
#define OP_RANKNET_CONFIG_TAG "ranknetconfig,r"     // ranknetconfig 比如 full-textual-full-categorical

unsigned CmdOption::numberOfTrainingConfigMax=200;
double CmdOption::startTestingLimitID=0;
 int CmdOption::startTrainingID=0;
 int CmdOption::endTrainingID=0;


bool CmdOption::has_help() const {
  return this->m_variable_map.count(OP_HELP);
}

void CmdOption::print_help() const {
  cout << this->m_visible_options << '\n';
}

double CmdOption::get_limit_k() const {
	return this->m_limit_k;
}

int CmdOption::get_trainingStartId() const {
  return this->trainingStartID;
}

int CmdOption::get_trainingEndId() const{
	return this->trainingEndID;

}



bool CmdOption::has_version() const {
  return this->m_variable_map.count(OP_VERSION);
}

void CmdOption::validate_options() {

  this->m_validation_error.clear();

  cout << "this is validate_options() function" << endl;

  /**
   * number of training duplicate reports.
   */
  if (this->m_variable_map.count(OP_TRAINING_DUPLICATES)) {
    const int number = this->m_variable_map[OP_TRAINING_DUPLICATES].as<int>();
    if (number < 0) {
      this->m_validation_error += "Option ";
      this->m_validation_error += OP_TRAINING_DUPLICATES;
      this->m_validation_error += " should be greater than or equal to 0\n";
    } else {
      this->m_training_duplicates = number;
      // change the limt of training
       CmdOption::numberOfTrainingConfigMax=number;
    }
  } else {
    this->m_training_duplicates = DEFAULT_TRAINING_REPORTS;
  }
  cout << "m_training_duplicates = " << this->m_training_duplicates << endl;

  /**
   * top k
   */
  if (this->m_variable_map.count(OP_TOP_K)) {
    const int k = this->m_variable_map[OP_TOP_K].as<int>();
    if (k <= 0) {
      this->m_validation_error += "Option ";
      this->m_validation_error += OP_TOP_K;
      this->m_validation_error += " should be greater than 0\n";
    } else {
        cout << "m_top_k = " << k << endl;
      this->m_top_k = k;
    }
  }



	/**
	 * limit numbers
	 */
	if (this->m_variable_map.count(OP_LIMIT_K)) {
		const double k = this->m_variable_map[OP_LIMIT_K].as<double>();
		if (k < 0) {
			fprintf(stdout,
					"Ops Ops Ops Ops Ops value is small ID LIMIT [(%f)ID /(%f) Start here]\n",
					k);
		      this->m_validation_error += "Option ";
		      this->m_validation_error += OP_LIMIT_K;
		      this->m_validation_error += " should be greater than 0\n";


		} else {
			fprintf(stdout,
					"INFO: value is not null ID LIMIT [(%f)ID /(%f) Start here]\n",
					k);

			this->m_limit_k = k;
			CmdOption::startTestingLimitID=k;
		}
	}
	cout << "m_limit_k = " << this->m_limit_k << endl;




	/**
	 * 2021-2-24 shangchao 不知道原来的 dbrd.v2是怎么编译的，和这一版的程序差不少啊！
	 * 我这里需要用命令行参数 -s 和 -e 来指定训练的 开始id和终止id
	 */
	/**
	 * start training ID
	 */
	if (this->m_variable_map.count(OP_START_TRAIN)) {
		const int k = this->m_variable_map[OP_START_TRAIN].as<int>();
		cout << "start training ID, k = " << k << endl;
		if (k < 0) {
			fprintf(stdout,
					"Start training ID LIMIT [(%f)ID /(%f) Start here]\n",
					k);
		      this->m_validation_error += "Option ";
		      this->m_validation_error += OP_START_TRAIN;
		      this->m_validation_error += " should be greater than 0\n";


		} else {
			fprintf(stdout,
					"INFO: Start Training ID [(%d)ID /(%d) Start here]\n",
					k);

			this->trainingStartID = k;
			CmdOption::startTrainingID=k;
		}
	}
    cout << "CmdOption.cc trainingStartId = " << this->startTrainingID << endl;



	/**
	 * end training ID
	 */
	if (this->m_variable_map.count(OP_END_TRAIN)) {
		const int k = this->m_variable_map[OP_END_TRAIN].as<int>();
		cout << "end training ID, k = " << endl;
		if (k < 0) {
			fprintf(stdout,
					" End training  ID LIMIT [(%f)ID /(%f) Start here]\n",
					k);
		      this->m_validation_error += "Option ";
		      this->m_validation_error += OP_END_TRAIN;
		      this->m_validation_error += " should be greater than 0\n";


		} else {
			fprintf(stdout,
					"INFO: End Training ID [(%d)ID /(%d) Start here]\n",
					k);

			this->trainingEndID = k;
			CmdOption::endTrainingID=k;
		}
	}
    cout << "CmdOption.cc trainingEndID = " << this->trainingEndID << endl;



  /*
   * iteration count
   */
  if (!(this->m_variable_map.count(OP_ITERATION))) {
    this->m_validation_error += "Option ";
    this->m_validation_error += OP_ITERATION;
    this->m_validation_error += " is not specified\n";
  } else {
    this->m_iteration = this->m_variable_map[OP_ITERATION].as<int>();
    if (this->m_iteration < 1) {
      this->m_validation_error += "Option ";
      this->m_validation_error += OP_ITERATION;
      this->m_validation_error += " should be an integer greater than 0\n";
    }
  }
  cout << "m_iteration = " << this->m_iteration << endl;

  /*
   * dataset path
   */
  if (!(this->m_variable_map.count(OP_DS))) {
    this->m_validation_error += "Dataset path is not specified\n";
  } else {
    this->m_dataset = this->m_variable_map[OP_DS].as<string>();
  }
  cout << "m_dataset = " << this->m_dataset;

  /*
   * project name
   */
  if (!(this->m_variable_map.count(OP_NAME))) {
    this->m_validation_error += "Project name is not specified\n";
  } else {
    this->m_project_name = this->m_variable_map[OP_NAME].as<string>();
  }
  cout << "m_project_name = " << this->m_project_name << endl;

  /*
   * recommendation file path
   */
  if (!(this->m_variable_map.count(OP_RECOMMENDATION)))
    this->m_recommendation_file = "";
  else
    this->m_recommendation_file = this->m_variable_map[OP_RECOMMENDATION].as<
        string>();

  cout << "m_recommendation_file = " << this->m_recommendation_file << endl;

  /*
   * whether to detect all reports, default, it is false
   */
  if (!(this->m_variable_map.count(OP_DETECT_ALL_REPORTS)))
    this->m_detect_all_reports = false;
  else {
    this->m_detect_all_reports = true;
  }
  cout << "m_detect_all_reports = " << this->m_detect_all_reports << endl;

  /*
   * indexing type
   */
  if (!(this->m_variable_map.count(OP_INDEXING_TYPE))) {
    this->m_indexing_type = IndexingType::NO_INDEXING;
  } else {
    this->m_indexing_type = IndexingType::parse_indexing_type(
        this->m_variable_map[OP_INDEXING_TYPE].as<int>());
  }
  cout << "m_indexing_type = " << this->m_indexing_type << endl;

#ifndef ONLY_ENABLE_RANKNET
  /*
   * detector type
   */
  if (!(this->m_variable_map.count(OP_DETECTOR_TYPE))) {
    this->m_validation_error += "Duplicate detector type is not specified\n";
    return;
  } else {
    this->m_detector_type = DetectorFactory::parse_detector_type(
        this->m_variable_map[OP_DETECTOR_TYPE].as<int>());

    switch (this->m_detector_type) {
      case DetectorFactory::SVM: {
        // SVM DETECTOR, e.g. ICSE10

        /*
         * feature vector calculator
         */
        if (!(this->m_variable_map.count(OP_FEATURE_CALCULATOR_TYPE))) {
          this->m_validation_error += "Option ";
          this->m_validation_error += OP_FEATURE_CALCULATOR_TYPE;
          this->m_validation_error += " is not specified\n";
        } else {
          int num_feature_calculator_type =
          this->m_variable_map[OP_FEATURE_CALCULATOR_TYPE].as<int>();
          if (num_feature_calculator_type < 1) {
            this->m_validation_error += "Option ";
            this->m_validation_error += OP_FEATURE_CALCULATOR_TYPE;
            this->m_validation_error += " should be an integer greater than 0\n";
          }
          this->m_feature_calculator_type =
          FeatureVectorCalculatorFactory::get_enum_feature_calculator_type_from_int(
              num_feature_calculator_type);
        }

        /*
         * evolve model duing detection?
         */
        if (!(this->m_variable_map.count(OP_EVOLVE_MODEL))) {
          this->m_validation_error += "Evloving model flag is not specified\n";
        } else {
          this->m_evolve_model = this->m_variable_map[OP_EVOLVE_MODEL].as<bool>();
        }
        break;
      }
      case DetectorFactory::PLAIN: {
        // PLAIN DETECTOR.
        if (!(this->m_variable_map.count(OP_PLAIN_MEASURE_TYPE))) {
          this->m_validation_error +=
          "Plain similarity measure type is not specified\n";
        } else {
          this->m_plain_measure_type =
          PlainSimilarityMeasureFactory::parse_similarity_measure_type(
              this->m_variable_map[OP_PLAIN_MEASURE_TYPE].as<int>());
        }
        break;
      }
      case DetectorFactory::RANK_NET: {
        // ranknet detector
        if (!(this->m_variable_map.count(OP_RANKNET_CONFIG))) {
          this->m_validation_error +=
          "RankNet config file path is not specified\n";
        } else {
          this->m_ranknet_config_file =
          this->m_variable_map[OP_RANKNET_CONFIG].as<string>();
        }
        break;
      }
      default:
      this->m_validation_error += "Unacceptable detector type\n";
      break;
    }
  }
#else
  this->m_detector_type = DetectorFactory::RANK_NET;
  // ranknet detector
  if (!(this->m_variable_map.count(OP_RANKNET_CONFIG))) {
    this->m_validation_error += "RankNet config file path is not specified\n";
  } else {
    this->m_ranknet_config_file = this->m_variable_map[OP_RANKNET_CONFIG].as<
        string>();
  }
  cout << "m_ranknet_config_file = " << this->m_ranknet_config_file << endl;
#endif
}

CmdOption::~CmdOption() {

}

CmdOption::CmdOption(int argc, char *argv[]) {
  this->m_training_duplicates = DEFAULT_TRAINING_REPORTS;
  this->m_iteration = 0;
  this->m_feature_calculator_type = FeatureVectorCalculatorFactory::NONE;
  this->m_plain_measure_type = PlainSimilarityMeasureFactory::NONE;
  this->m_detector_type = DetectorFactory::NONE;
  this->m_evolve_model = false;
  this->m_project_name = "";
  this->m_dataset = "";
  this->m_detect_all_reports = false;
  this->m_top_k = 20;
  this->m_limit_k = 0;
  this->m_indexing_type = IndexingType::NO_INDEXING;
  this->trainingStartID=0;
  this->trainingEndID=9999999;


  /**
   * generic options.
   *
   * --version
   *
   * --help
   *
   */
  po::options_description generic_options("Generic Options");
  generic_options.add_options()(OP_VERSION_TAG, "print version string")(
      OP_HELP_TAG, "produce help message");

  /**
   * necessary options:
   *
   * --n=<negative-min-support>
   *
   * --k=<top-k>
   */
  po::options_description necessary_options("Necessary options");

#ifndef ONLY_ENABLE_RANKNET

  necessary_options.add_options()(OP_FEATURE_CALCULATOR_TYPE_TAG,
      po::value<int>(),
      ("feature calculator type. "
          + FeatureVectorCalculatorFactory::get_enum_feature_calculator_type_mapping()).c_str());

  necessary_options.add_options()(OP_PLAIN_MEASURE_TYPE_TAG, po::value<int>(),
      ("plain measure type. "
          + PlainSimilarityMeasureFactory::get_similarity_measure_type_mapping()).c_str());

  necessary_options.add_options()(OP_DETECTOR_TYPE_TAG, po::value<int>(),
      DetectorFactory::get_detector_type_mapping().c_str());

  necessary_options.add_options()(OP_EVOLVE_MODEL_TAG, po::value<bool>(),
      "whether to evlove the model");
#endif

  necessary_options.add_options()(OP_INDEXING_TYPE_TAG, po::value<int>(),
      IndexingType::get_indexing_type_mapping().c_str());

  necessary_options.add_options()(OP_TRAINING_DUPLICATES_TAG, po::value<int>(),
      "number of duplicates to construct the training set. The default value is 200");

  necessary_options.add_options()(OP_TOP_K_TAG, po::value<int>(),
      "number of reports to retrieve for each new report");


  necessary_options.add_options()(OP_LIMIT_K_TAG, po::value<double>(),
			"limit to search after is. The default value is 0");



  necessary_options.add_options()(OP_START_TRAIN_TAG, po::value<int>(),
			"start training ID to search after is. The default value is 0");

  necessary_options.add_options()(OP_END_TRAIN_TAG, po::value<int>(),
  			"end training ID to search after is. The default value is 0");



  necessary_options.add_options()(OP_ITERATION_TAG, po::value<int>(),
      "number of iterations");
  necessary_options.add_options()(OP_NAME_TAG, po::value<string>(),
      "project name");
  necessary_options.add_options()(OP_RECOMMENDATION_TAG, po::value<string>(),
      "recommendation file");

  necessary_options.add_options()(OP_RANKNET_CONFIG_TAG, po::value<string>(),
      "ranknet model config file.");

  necessary_options.add_options()(OP_DETECT_ALL_REPORTS_TAG,
      "whether to detect all reports");

  po::options_description hidden_options("Hidden options");
  hidden_options.add_options()(OP_DS_TAG, po::value<string>(), "dataset file");

  po::options_description cmdline_options;
  cmdline_options.add(generic_options);
  cmdline_options.add(necessary_options);
  cmdline_options.add(hidden_options);

  this->m_visible_options.add(necessary_options);
  this->m_visible_options.add(generic_options);

  po::positional_options_description dataset_option;
  dataset_option.add(OP_DS_TAG, 1);

  try {
    po::store(
        po::command_line_parser(argc, argv).options(cmdline_options).positional(
            dataset_option).run(), m_variable_map);
    po::notify(m_variable_map);
  } catch (std::exception& e) {
    this->m_parsing_error += e.what();
  }
}

